/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package biofilter.filters;

import biofilter.exceptions.InvalidRecordException;
import biofilter.records.*;
import biofilter.util.*;
import biofilter.sources.*;
import java.util.*;

/**
 * This is a join-split filter.
 * It works as follows: Records that implement the MappedPosition interface
 * stream into the filter from a compatable RecordsSource.  These records are NOT
 * stored.  Histogram records are then created for each chomosome/plasmid mapped in the original
 * mapping run.  Then for each read, call the map interface on the histogram given the
 * next mapped position from the records file.  Finally when getRecord is called on the filter,
 * return the next histogramRecord in the output queue.
 * @author Daniel Quest
 * @date   Dec 22 2009
 */
public class HistogramFilter extends JoinSplitFilterBase {

    private char MappingPolicy = 'a';  //--all; map everything regardless (default)
                               //'u';  //--unique require that reads must map back to only one position

    public HistogramFilter(){
        super();
        this.setRequestNum(1);
        this.unique = false;
    }

    public HistogramFilter(RecordsFile rf){
        super(rf);
        this.setRequestNum(1);
        this.unique = false;
    }

    public void setSource(RecordsFile rf){
        this.setSource(rf);
    }

    private int mapmax = Integer.MAX_VALUE;
    public void setMapMax(int max){
        mapmax = max;
    }

    private HistogramRecord getHistogram(String subjectID){
        HistogramRecord hr = null;
        ListIterator iter = this.outputQueue.listIterator();
        while (iter.hasNext() == true){
            hr = (HistogramRecord) iter.next();
            if (hr.targetMatches(subjectID) == true){
                //System.err.println("Using an old histogram! "+ subjectID);
                return hr;
            }
        }
        //else, subjectID could not be found in the list, we need to add it
        //System.err.println("Creating a new histogram! "+ subjectID);
        hr = new HistogramRecord();
        if(this.mapmax != Integer.MAX_VALUE){
            hr.setMapMax(this.mapmax);
        }
        hr.setTarget(subjectID);
        this.outputQueue.add(hr);
        return hr;
    }

    boolean unique;
    Stack previousPositions = null;
    public void unique(){
        this.unique = true;
        this.previousPositions = new Stack();
    }
    public boolean getUnique(){ return this.unique; }

    public void setNormalizePerRead(boolean b){ normalizePerRead = b; }
    public void setRawScore(boolean b){ rawScore = b; }
    private boolean normalizePerRead = false; //normalizePerRead==false will count exactly 1 for each mapped read
                                              //normalizePerRead==true will count the alignment score/length for each read in the alignment
    private boolean rawScore = false; //rawScores==true will add the score given to the histogram at the desired position
                                       //rawScores==false will add the score divided by the length at the desired position
    public double weight(MappedPosition mp){
        double weight = 1.0;
        if (this.rawScore == true){
            weight = mp.getWeight();
        }
        if (normalizePerRead == true){
            weight = mp.getWeight() / mp.length();
        }
        return weight;
    }

    @Override
    protected Record transformRecord(Record r) throws InvalidRecordException {
        HistogramRecord hr = null;
        int start=0, end=0;
        //while more MappedPossitions exist in the input queue
        while(this.inputQueue.isEmpty() == false){
                //operations on the Record object
                //if ( (r instanceof MappedPosition) == false ){
                //    throw new InvalidRecordException("HistogramFilter must be passed records that conform to the biofilter.util.MappedPosition interface");
                //}
            MappedPosition mp = (MappedPosition) this.inputQueue.removeFirst();
            hr = getHistogram(mp.getSubjectID());
            if (this.unique == false){
                //System.out.println(mp.getStart() + " " + mp.getEnd() + " " + mp.getStrand() + " " + mp.getWeight());
                hr.map(mp.getStart(), mp.getEnd(), mp.getStrand(), this.weight(mp));
                //System.err.println(hr.get());
            }else {
                if (this.previousPositions.empty()){
                    this.previousPositions.push(mp);
                }else {
                    MappedPosition prev = (MappedPosition) this.previousPositions.peek();
                    //if the previous element on the stack does not match the query id for the current record, they can't be related,
                    //therefore we have all elements of the current query in the stack
                    if (!prev.getQueryID().equalsIgnoreCase(mp.getQueryID())){
                        if(this.previousPositions.size() == 1) mapStack(hr);  //only if it contains 1 element is it unique
                        //clear the stack
                        this.previousPositions.removeAllElements();
                        this.previousPositions.push(mp);
                    }else { //same query
                        this.previousPositions.push(mp);
                    }
                }
            }
            //get the next record from the stream...
            if(!eof() && !source.eof() ){
                this.originalRecord = source.getRecord();
                if ( this.originalRecord != null) 
                    this.inputQueue.add( this.originalRecord);
            }
        }//end while
        if(this.previousPositions != null){
            if(this.previousPositions.size() == 1 ) mapStack(hr); //only if it contains 1 element and is it unique
        }  

        return (Record) this.outputQueue.removeFirst();
    }

    /**
     * maps a stack of MappedPositions (e.g. alignments) onto histogram record hr
     * @param hr
     */
    private void mapStack(HistogramRecord hr){
        if (this.previousPositions == null) return;
        for( int i = 0; i<this.previousPositions.size(); i++){
            MappedPosition mp = (MappedPosition) this.previousPositions.get(i);
            //System.err.println("Mapping: " + mp.getStart() + " " + mp.getEnd() + " " + mp.getStrand());
            hr.map(mp.getStart(), mp.getEnd(), mp.getStrand(), this.weight(mp) );
        }
    }

    public Record peek() throws InvalidRecordException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

}
